﻿using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Sigil;

namespace Jil.Common
{
	internal static class EnumValues<TEnum>
			where TEnum : struct
	{
		private static readonly Dictionary<string, TEnum> PreCalculated;

		static EnumValues()
		{
			PreCalculated = new Dictionary<string, TEnum>(StringComparer.InvariantCultureIgnoreCase);

			foreach (var val in Enum.GetValues(typeof(TEnum)))
			{
				var name = typeof(TEnum).GetEnumValueName(val);
				PreCalculated[name] = (TEnum)val;
			}
		}

		public static bool TryParse(string str, out TEnum ret)
		{
			return PreCalculated.TryGetValue(str, out ret);
		}
	}

	internal static class EnumValues
	{
		private delegate bool TryParseProxy(string str, out object val);

		private static readonly Hashtable Cache = new Hashtable();

		public static bool TryParse(Type enumType, string str, out object ret)
		{
			var invoke = (TryParseProxy)Cache[enumType];
			if (invoke != null) return invoke(str, out ret);

			var emit = Emit<TryParseProxy>.NewDynamicMethod(doVerify: Utils.DoVerify);
			var specific = typeof(EnumValues<>).MakeGenericType(enumType).GetMethod("TryParse", BindingFlags.Public | BindingFlags.Static);

			using (var loc = emit.DeclareLocal(enumType))
			{
				emit.LoadArgument(0);           // string
				emit.LoadLocalAddress(loc);     // string enum&
				emit.Call(specific);            // bool
				emit.LoadArgument(1);           // bool object&
				emit.LoadLocal(loc);            // bool object& enum
				emit.Box(enumType);             // bool object& object
				emit.StoreIndirect<object>();   // bool
				emit.Return();                  // --empty--
			}

			var parse = emit.CreateDelegate(Utils.DelegateOptimizationOptions);

			lock (Cache)
			{
				if (Cache.ContainsKey(enumType))
				{
					parse = (TryParseProxy)Cache[enumType];
				}
				else
				{
					Cache[enumType] = parse;
				}
			}

			return parse(str, out ret);
		}
	}
}